#include <random>
#include <chrono>
#include <iostream>

#include <QApplication>
#include <QMainWindow>
#include <QWidget>
#include <QSurface>

#include "utilities.h"
#include "FireworkManager.h"
#include "DataSingleton.h"

#include "VsgManager.h"
#include "ViewerWindow.h"

struct VsgManagerData
{
    FireworkManager manager;
    vsgQt::ViewerWindow* qtWindow;
    vsg::time_point timeStamp {vsg::clock::now()};
};

void VsgManager::init()
{
    initData();
    initScene();
    initWindow();
}

void VsgManager::initScene()
{
    m_scene = vsg::StateGroup::create();
    InitGraphPipeline();
    m_sceneData = vsg::Group::create();
    m_scene->addChild(m_sceneData);
    m_sceneData->addChild(m_data->manager.getNode());
}

void VsgManager::InitGraphPipeline()
{
    using namespace vsg;
    vsg::Paths searchPaths = vsg::getEnvPaths("VSG_FILE_PATH");
    vsg::ref_ptr<vsg::ShaderStage> vertexShader = vsg::ShaderStage::read(VK_SHADER_STAGE_VERTEX_BIT, "main", vsg::Path("./shaders/firework_vsg_vert.spv"));
    vsg::ref_ptr<vsg::ShaderStage> fragmentShader = vsg::ShaderStage::read(VK_SHADER_STAGE_FRAGMENT_BIT, "main", vsg::Path("./shaders/firework_vsg_frag.spv"));
    if (!vertexShader || !fragmentShader)
    {
        std::cout << "Could not create shaders." << std::endl;
    }

    vsg::PushConstantRanges pushConstantRanges
        {
            {VK_SHADER_STAGE_VERTEX_BIT, 0, 128} // projection view, and model matrices, actual push constant calls automatically provided by the VSG's DispatchTraversal
        };

    vsg::VertexInputState::Bindings vertexBindingsDescriptions
        {
            VkVertexInputBindingDescription{0, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX}, // vertex data
            VkVertexInputBindingDescription{1, sizeof(vsg::vec4), VK_VERTEX_INPUT_RATE_VERTEX}, // colour data
        };

    vsg::VertexInputState::Attributes vertexAttributeDescriptions
        {
            VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // vertex data
            VkVertexInputAttributeDescription{1, 1, VK_FORMAT_R32G32B32A32_SFLOAT, 0}, // colour data
        };

    vsg::GraphicsPipelineStates pipelineStates
        {
         vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions),
         vsg::InputAssemblyState::create(),
         vsg::RasterizationState::create(),
         vsg::MultisampleState::create(),
         vsg::ColorBlendState::create(),
         vsg::DepthStencilState::create() };
    auto descriptorSetLayout = vsg::DescriptorSetLayout::create();
    auto pipelineLayout = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout}, pushConstantRanges);
    auto graphicsPipeline = vsg::GraphicsPipeline::create(pipelineLayout, vsg::ShaderStages{ vertexShader, fragmentShader }, pipelineStates);
    auto bindGraphicsPipeline = vsg::BindGraphicsPipeline::create(graphicsPipeline);

    auto descriptorSet = vsg::DescriptorSet::create(descriptorSetLayout, vsg::Descriptors{});
    auto bindDescriptorSet = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, descriptorSet);

    // create StateGroup as the root of the scene/command graph to hold the GraphicsProgram, and binding of Descriptors to decorate the whole graph
    m_scene->add(bindGraphicsPipeline);
    m_scene->add(bindDescriptorSet);
}

int VsgManager::run(int argc, char** argv)
{
    QApplication application(argc, argv);
    init();

    QMainWindow* mainWindow = new QMainWindow();

    auto widget = QWidget::createWindowContainer(m_data->qtWindow, mainWindow);
    mainWindow->setCentralWidget(widget);

    mainWindow->move(150, 150);
    mainWindow->resize(800, 600);
//    mainWindow->setWindowFlag(Qt::FramelessWindowHint);
    mainWindow->show();

    return application.exec();
}

void VsgManager::update(std::chrono::nanoseconds delt)
{
    m_data->manager.update(delt);
}

void VsgManager::initWindow()
{
    m_data->qtWindow->initializeCallback = [&](vsgQt::ViewerWindow & vw, uint32_t width, uint32_t height)
    {

        auto& window = vw.windowAdapter;
        if (!window)
            return false;
        window->clearColor() = VkClearColorValue{ {0, 0, 0, 1} };

        auto& viewer = vw.viewer;
        if (!viewer)
            viewer = vsg::Viewer::create();

        viewer->addWindow(window);

        vsg::ref_ptr<vsg::LookAt> lookAt;
        vsg::dvec3 eye{ -50., -50., 20.0 };
        vsg::dvec3 center = { 70., 30., 70. };
        vsg::dvec3 up = vsg::dvec3(0.0, 0.0, 1.0);
        lookAt = vsg::LookAt::create(eye, center, up);

        double nearFarRatio = 0.01;
        auto perspective = vsg::Perspective::create(70.0, static_cast<double>(width) / static_cast<double>(height), nearFarRatio, 1000);

        auto camera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(window->extent2D()));

        // add close handler to respond the close window button and pressing escape
        viewer->addEventHandler(vsg::CloseHandler::create(viewer));

        // add trackball to enable mouse driven camera view control.
        viewer->addEventHandler(vsg::Trackball::create(camera));

        auto commandGraph = vsg::createCommandGraphForView(window, camera, m_scene);
        viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});

        viewer->compile();

        return true;
    };

    m_data->qtWindow->frameCallback = [&](vsgQt::ViewerWindow & vw)
    {

        if (!vw.viewer || !vw.viewer->advanceToNextFrame())
        {
            return false;
        }

        vw.viewer->handleEvents();

        auto timeNow = vsg::clock::now();
        std::chrono::nanoseconds delt = timeNow - m_data->timeStamp;
        m_data->timeStamp = timeNow;
        update(delt);

        vw.viewer->update();

        vw.viewer->recordAndSubmit();

        vw.viewer->present();

        return true;
    };
}

void VsgManager::initData()
{
    m_data = new VsgManagerData();
    m_data->manager.setFireworkCount(30);

    //DataSingleton::instance()->m_device = m_window->getOrCreateDevice();

    m_data->qtWindow = new vsgQt::ViewerWindow;
#if QT_HAS_VULKAN_SUPPORT
    m_data->qtWindow->setSurfaceType(QSurface::VulkanSurface);
#endif
    auto windowTraits = vsg::WindowTraits::create();
    m_data->qtWindow->traits = windowTraits;
}


